/* 
Scenic Graphics Library
Copyright (C) 2007 Jouni Tulkki

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USApackage scenic;
*/

package scenic.path;

import java.awt.geom.Point2D;
import java.util.Vector;

/**
 * This class offers a convenient interface for creating paths.
 */
public class PathBuilder {
	private Vector<SubPath> subPaths;
	private Vector<Segment> segments;
	private double startX;
	private double startY;
	boolean hasStartPoint = false;
	
	/**
	 * Creates a new PathBuilder object.
	 */
	public PathBuilder() {
		subPaths = new Vector<SubPath>();
		segments = new Vector<Segment>();
	}

	/**
	 * Begins a new subpath from the given position.
	 */
	public void moveTo(double x, double y) {
		if(hasStartPoint)
			endSubPath(false);
		startX = x;
		startY = y;
		hasStartPoint = true;
	}
	
	/**
	 * Draws a straight line to the given position. If not start point
	 * has been set, the function sets the start point.
	 */
	public void lineTo(double x, double y) {
		if(hasStartPoint)
			segments.add(new LineSegment(x, y));
		else
			moveTo(x, y);
	}
	
	/**
	 * Draws a quadratic Bezier-curve using the given
	 * control points. The first control point is the current
	 * position.
	 * 
	 * @param p1 the second control point.
	 * @param p2 the third control point.
	 */
	public void curveTo(Point2D p1, Point2D p2) {
		addSegment(new QuadraticBezierSegment(p1.getX(), p1.getY(), p2.getX(), p2.getY()));
	}
	
	/**
	 * Draws a cubic Bezier-curve using the given
	 * control points. The first control point is the current
	 * position.
	 * 
	 * @param p1 the second control point.
	 * @param p2 the third control point.
	 * @param p3 the fourth control point.
	 */
	public void curveTo(Point2D p1, Point2D p2, Point2D p3) {
		addSegment(new CubicBezierSegment(
				p1.getX(), p1.getY(), 
				p2.getX(), p2.getY(), 
				p3.getX(), p3.getY()));
	}
	
	/**
	 * Draws an elliptic arc starting from the current position.
	 * 
	 * @param xRadius the x radius of the ellipse.
	 * @param yRadius the y radius of the ellipse.
	 * @param rotation the rotation of the ellipse in degrees.
	 * @param startAngle the start angle of the arc in degrees.
	 * @param stopAngle the stop angle of the arc in degrees.
	 */
	public void arcTo(double xRadius, double yRadius, double rotation, 
			double startAngle, double stopAngle)
	{
		addSegment(new ArcSegment(xRadius, yRadius, rotation,
				startAngle, stopAngle));
	}
	
	/**
	 * Draws an elliptic arc starting from the given position.
	 * 
	 * @param x the x-coordinate of the starting point of the arc.
	 * @param y the y-coordinate of the starting point of the arc.
	 * @param xRadius the x radius of the ellipse.
	 * @param yRadius the y radius of the ellipse.
	 * @param rotation the rotation of the ellipse in degrees.
	 * @param startAngle the start angle of the arc in degrees.
	 * @param stopAngle the stop angle of the arc in degrees.
	 */
	public void arc(double x, double y, double xRadius, 
			double yRadius, double rotation, 
			double startAngle, double stopAngle)
	{
		ArcSegment s = new ArcSegment(xRadius, yRadius, rotation,
				startAngle, stopAngle);
		Point2D.Double p = s.calcRelativePoint(0.0);
		lineTo(x + p.x, y + p.y);
		addSegment(s);
	}
	
	/**
	 * Draws a rectangle.
	 * 
	 * @param x the left side of the rectangle.
	 * @param y the top of the rectangle.
	 * @param width the width of the rectangle.
	 * @param height the height of the rectangle.
	 */
	public void rectangle(double x, double y, double width, double height) {
		moveTo(x, y);
		lineTo(x + width, y);
		lineTo(x + width, y + height);
		lineTo(x, y + height);
		close();		
	}
	
	/**
	 * Draws a rectangle with rounded edges. 
	 * 
	 * @param x the left side of the rectangle.
	 * @param y the top of the rectangle.
	 * @param width the width of the rectangle.
	 * @param height the height of the rectangle.
	 * @param radius the radius of the rounded edges.
	 */
	public void roundedRectangle(double x, double y, double width, double height, 
			double radius) {
		if(radius * 2.0 > Math.min(width, height))
			radius = Math.min(width, height) / 2.0;
		
		moveTo(x + radius, y);
		lineTo(x + width - radius, y);
		arcTo(radius, radius, 0, -90, 0);
		lineTo(x + width, y + height - radius);
		arcTo(radius, radius, 0, 0, 90);
		lineTo(x + radius, y + height);
		arcTo(radius, radius, 0, 90, 180);
		lineTo(x, y + radius);
		arcTo(radius, radius, 0, 180, 270);
		close();		
	}

	/**
	 * Adds a segment to the current subpath.
	 * 
	 * @param s the segment to be added.
	 */
	public void addSegment(Segment s) {
		if(!hasStartPoint)
			throw new IllegalStateException();
		segments.add(s);
	}
	
	/**
	 * Closes the current subpath.
	 */
	public void close() {
		endSubPath(true);
	}
	
	private void endSubPath(boolean closed) {
		if(segments.size() > 0) {
			SubPath subPath = new SubPath(startX, startY, 
					(Segment[])segments.toArray(new Segment[segments.size()]),
					closed);
			subPaths.add(subPath);
			segments.clear();
		}
		hasStartPoint = false;
	}
	
	/**
	 * Creates a Path object that contains the created path.
	 * 
	 * @return the Path object.
	 */
	public Path createPath() {
		endSubPath(false);
		
		return new GenericPath((SubPath[])subPaths.toArray(new SubPath[subPaths.size()]));
	}
	
	/**
	 * Get the subpaths as an array.
	 * 
	 * @return the subpaths
	 */
	public SubPath[] getSubPaths() {
		endSubPath(false);
		
		return (SubPath[])subPaths.toArray(new SubPath[subPaths.size()]);
	}
}
